#include <unistd.h>

#include <libavcodec/avcodec.h>
#include <libavformat/avformat.h>
#include <libavfilter/avcodec.h>
#include <libavutil/frame.h>

#include <SDL/SDL.h>

static AVFormatContext *fmt_ctx;
static int video_stream_index = -1;
static AVCodecContext *dec_ctx;

SDL_Surface *pscreen;
SDL_Overlay *poverlay;

static int open_input_file(const char *filename) {
    int ret;
    AVCodec *dec;

    if ((ret = avformat_open_input(&fmt_ctx, filename, NULL, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open input file\n");
        return ret;
    }

    if ((ret = avformat_find_stream_info(fmt_ctx, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot find stream information\n");
        return ret;
    }

    /* select the video stream */
    ret = av_find_best_stream(fmt_ctx, AVMEDIA_TYPE_VIDEO, -1, -1, &dec, 0);
    if (ret < 0) {
        av_log(NULL, AV_LOG_ERROR,
                "Cannot find a video stream in the input file\n");
        return ret;
    }
    video_stream_index = ret;
    dec_ctx = fmt_ctx->streams[video_stream_index]->codec;

    /* init the video decoder */
    if ((ret = avcodec_open2(dec_ctx, dec, NULL)) < 0) {
        av_log(NULL, AV_LOG_ERROR, "Cannot open video decoder\n");
        return ret;
    }

    return 0;
}

int SDL_init(int width, int height) {
    atexit(SDL_Quit);

    if (SDL_Init(SDL_INIT_VIDEO) < 0) {
        return -1;
    }
    pscreen = SDL_SetVideoMode(width, height, 24, SDL_SWSURFACE);
    if (pscreen == NULL) {
        return -1;
    }
    poverlay = SDL_CreateYUVOverlay(width, height, SDL_YV12_OVERLAY, pscreen);
    if (poverlay == NULL) {
        return -1;
    }

    return 0;
}

int flip(int width, int height, int num, AVFrame *frame) {
    unsigned char *op[3];
    int y;
    SDL_Rect rect;
    rect.w = width;
    rect.h = height;
    rect.x = 0;
    rect.y = 0;
    SDL_LockSurface(pscreen);
    SDL_LockYUVOverlay(poverlay);

    for (y = 0; y < pscreen->h && y < poverlay->h; y++) {
        op[0] = poverlay->pixels[0] + poverlay->pitches[0] * y;
        op[1] = poverlay->pixels[1] + poverlay->pitches[1] * (y / 2);
        op[2] = poverlay->pixels[2] + poverlay->pitches[2] * (y / 2);
        memcpy(op[0], frame->data[0] + y * frame->linesize[0], width);
        if (y % 2 == 0) {
            memcpy(op[2], frame->data[1] + (y / 2) * frame->linesize[1],
                    width / 2);
            memcpy(op[1], frame->data[2] + (y / 2) * frame->linesize[2],
                    width / 2);
        }
    }
    SDL_UnlockYUVOverlay(poverlay);
    SDL_UnlockSurface(pscreen);
    SDL_DisplayYUVOverlay(poverlay, &rect);

    char pnum[10];
    sprintf(pnum, "#%d", num);
    SDL_WM_SetCaption(pnum, NULL);

    return 0;
}

int main(int argc, char **argv) {
    int ret;
    AVPacket packet;
    AVFrame *frame = av_frame_alloc();
    int got_frame;
    FILE *fpOut;

    int frame_num = 0;

    if (!frame) {
        perror("Could not allocate frame");
        exit(1);
    }
    if (argc != 2) {
        fprintf(stderr, "Usage: %s file\n", argv[0]);
        exit(1);
    }

    fpOut = fopen("a.yuv", "wb");

    avcodec_register_all();
    av_register_all();

    if ((ret = open_input_file(argv[1])) < 0)
        goto end;

    int width = fmt_ctx->streams[video_stream_index]->codec->width;
    int height = fmt_ctx->streams[video_stream_index]->codec->height;
    SDL_init(width, height);

    /* read all packets */
    while (1) {
        if ((ret = av_read_frame(fmt_ctx, &packet)) < 0)
            break;

        if (packet.stream_index == video_stream_index) {
            avcodec_get_frame_defaults(frame);
            got_frame = 0;
            ret = avcodec_decode_video2(dec_ctx, frame, &got_frame, &packet);
            if (ret < 0) {
                av_log(NULL, AV_LOG_ERROR, "Error decoding video\n");
                break;
            }

            frame_num++;
            if (got_frame) {
                /*
                 printf("<%d>: %dx%d, %d\n", frame_num, frame->width,
                 frame->height, frame->channels);
                 for (i = 0; i < frame->height; i++) {
                 fwrite(frame->data[0] + i * frame->linesize[0], 1,
                 frame->width, fpOut);
                 }
                 for (i = 0; i < frame->height / 2; i++) {
                 fwrite(frame->data[1] + i * frame->linesize[1], 1,
                 frame->width / 2, fpOut);
                 }
                 for (i = 0; i < frame->height / 2; i++) {
                 fwrite(frame->data[2] + i * frame->linesize[2], 1,
                 frame->width / 2, fpOut);
                 }
                 if (frame_num >= 150) {
                 break;
                 }
                 */
                flip(width, height, frame_num, frame);
                //SDL Event handler
                SDL_Event sdl_event;
                while (SDL_PollEvent(&sdl_event)) {
                    switch (sdl_event.type) {
                    case SDL_KEYDOWN:
                        break;
                    case SDL_KEYUP:
                        if (sdl_event.key.keysym.sym == SDLK_q) {
                            return 0;
                        }
                        break;
                    case SDL_QUIT:
                        printf("OnQuit\n");
                        return 0;
                    default:
                        break;
                    }
                }
            }
        }
        av_free_packet(&packet);
    }
    end: fclose(fpOut);

    if (dec_ctx)
        avcodec_close(dec_ctx);
    avformat_close_input(&fmt_ctx);
    av_frame_free(&frame);

    if (ret < 0 && ret != AVERROR_EOF) {
        char buf[1024];
        av_strerror(ret, buf, sizeof(buf));
        fprintf(stderr, "Error occurred: %s\n", buf);
        exit(1);
    }

    exit(0);
}

